package com.apobates.jforum.grief.schema2doc.jdbc.schema;

import com.apobates.jforum.grief.schema2doc.core.schema.SchemaDialect;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 数据源配置
 */
public final class ResultSupplier {
    private final Builder builder;
    private final String username;
    private final String password;
    private final static Logger logger = LoggerFactory.getLogger(ResultSupplier.class);
    /**
     *
     * @param builder
     * @param username
     * @param password
     */
    private ResultSupplier(Builder builder, String username, String password){
        this.builder = builder;
        this.username = username;
        this.password = password;
    }

    /**
     * 使用Hikari创建DataSource
     * @return
     */
    public DataSource create(){
            // 数据源  创建HikariConfig配置类
            HikariConfig hikariConfig = new HikariConfig();
            hikariConfig.setDriverClassName(builder.driverClassName);
            // 后期改由SchemaDialect来实现
            hikariConfig.setJdbcUrl(builder.dialect.buildJdbcURL(builder.dbUrl, builder.database, builder.schema));
            hikariConfig.setUsername(this.username);
            hikariConfig.setPassword(this.password);
            // 设置useInformationSchema 可以获取tables表注释信息 即解决数据库表和列字段有说明、生成文档没有说明
            hikariConfig.addDataSourceProperty("useInformationSchema", "true");
            hikariConfig.setMinimumIdle(2);
            hikariConfig.setMaximumPoolSize(5);
            return new HikariDataSource(hikariConfig);

    }

    /**
     * 使用指定的数据方言创建导出信息查询执行器
     * @return
     * @throws IllegalStateException
     */
    public AbstractQueryExecutor executor() throws IllegalStateException{
        if(builder.dialect == SchemaDialect.ETC){
            throw new IllegalStateException("无法定位实现类");
        }
        try {
            String className = String.format("com.apobates.jforum.grief.schema2doc.jdbc.dialect.JooqQuery%sExecutor", builder.dialect.name());
            logger.debug(String.format("正在使用的方言:%s, 执行器:%s", builder.dialect.name(), className));
            return executor((Class<? extends AbstractQueryExecutor>) Class.forName(className));
        }catch (ClassNotFoundException e){
            throw new IllegalStateException("实现类无法加载");
        }
    }

    /**
     * 使用指定的导出信息查询执行器
     * @param classIns
     * @return
     * @throws IllegalStateException
     */
    public AbstractQueryExecutor executor(Class<? extends AbstractQueryExecutor> classIns) throws IllegalStateException{
        try {
            Constructor<? extends AbstractQueryExecutor> declaredConstructor = classIns.getDeclaredConstructor(DataSource.class, SchemaDialect.class);
            AbstractQueryExecutor instance = declaredConstructor.newInstance(create(), builder.dialect);
            return instance;
        }catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e){
            throw new NullPointerException("参数类实例化失败. 参数:"+classIns.getName());
        }
    }
    /**
     * 创建Mysql数据源配置器
     * 驱动: com.mysql.cj.jdbc.Driver
     * @see //dev.mysql.com/doc/connector-j/8.1/en/connector-j-usagenotes-connect-drivermanager.html
     * @return
     */
    public static Builder mysql(){
        return new Builder(SchemaDialect.MySQL).className("com.mysql.cj.jdbc.Driver"); // .port(3306);
    }
    /**
     * 创建Oracle数据源配置器
     * 驱动: oracle.jdbc.driver.OracleDriver
     * @return
     */
    public static Builder oracle(){
        return new Builder(SchemaDialect.Oracle).className("oracle.jdbc.driver.OracleDriver"); // .port(1521);
    }
    /**
     * 创建SQLServer数据源配置器
     * 驱动: com.microsoft.sqlserver.jdbc.SQLServerDriver
     * 模式: dbo
     * @see //learn.microsoft.com/zh-cn/sql/connect/jdbc/working-with-a-connection?view=sql-server-ver16
     * @return
     */
    public static Builder mssql(){
        return new Builder(SchemaDialect.SQLServer).className("com.microsoft.sqlserver.jdbc.SQLServerDriver").schema("dbo"); // .port(1433);
    }
    /**
     * 创建PostgreSQL数据源配置器
     * 驱动: org.postgresql.Driver
     * 模式: public
     * @see //jdbc.postgresql.org/documentation/use/
     * @return
     */
    public static Builder postgresql(){
        return new Builder(SchemaDialect.PostgreSQL).className("org.postgresql.Driver").schema("public"); //.port(5432);
    }
    /**
     * 创建MariaDB数据源配置器
     * 驱动: org.mariadb.jdbc.Driver
     * @see //mariadb.com/kb/en/about-mariadb-connector-j/
     * @return
     */
    public static Builder mariadb(){
        return new Builder(SchemaDialect.MariaDB).className("org.mariadb.jdbc.Driver");  //.port(3306);
    }

    /**
     * 创建SQLite数据源配置器
     * 驱动: org.sqlite.JDBC
     * @see //www.sqlite.org/java/raw/doc/overview.html?name=0a704f4b7294a3d63e6ea2b612daa3b997c4b5f1
     * @return
     */
    public static Builder sqlite(){
        return new Builder(SchemaDialect.SQLite).className("org.sqlite.JDBC");  //.port();
    }

    /**
     * 创建Informix数据源配置器
     * 驱动: com.informix.jdbc.IfxDriver
     * @see //www.ibm.com/docs/en/informix-servers/12.10?topic=database-load-informix-jdbc-driver
     * @return
     */
    public static Builder informix(){
        return new Builder(SchemaDialect.Informix).className("com.informix.jdbc.IfxDriver"); //.port(1533);
    }

    /**
     * 创建Sybase数据源配置器
     * 驱动: com.sybase.jdbc.SybDriver
     * @see //infocenter-archive.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc00170.1270/html/iqapg/Jconnect_using_jdbxextra.htm
     * @return
     */
    public static Builder sybase(){
        return new Builder(SchemaDialect.Sybase).className("com.sybase.jdbc.SybDriver");  //.port(1533);
    }
    /**
     * 数据源配置器
     */
    public static class Builder{
        private final SchemaDialect dialect;
        private String driverClassName;
        private String dbUrl;
        // 0表示不需往driverClassName上附件
        // private int port=0;
        // private String charset="utf8";
        // oracle的SID或服务名
        // private String service;
        // 数据库名称
        private String database;
        // 模式名称
        // 适用于: Oracle/PostgreSQL
        private String schema;
        public Builder(SchemaDialect dialect) {
            this.dialect = dialect;
        }

        /**
         * 设置jdbc驱动类全名
         * @param driverClassName 驱动类全名
         * @return
         */
        public Builder className(String driverClassName){
            this.driverClassName = driverClassName;
            return this;
        }

        /**
         * 设置jdbc连接地址,需要包含端口号
         * Oracle:
         *  1)需要包含SID或ServiceName和数据库名称。
         *  2)同时需要再调用schema
         * PostgreSQL:
         *  1)地址中不需要包括数据库名称和模式,通过相应的方法来设置
         * @param dbUrl 连接地址
         * @return
         */
        public Builder url(String dbUrl){
            this.dbUrl = dbUrl;
            return this;
        }

        /**
         * 设置连接的数据库名称
         * MySQL 不需要设置
         * SQLServer 数据库名称, 若URL中包含了也不用设置
         * 注意: Oracle调用无效果
         * @param database
         * @return
         */
        public Builder database(String database){
            if(dialect!=SchemaDialect.Oracle) {
                this.database = database;
            }
            if(dialect == SchemaDialect.MariaDB || dialect == SchemaDialect.MySQL || dialect == SchemaDialect.SQLite){
                this.schema = database;
            }
            return this;
        }

        /**
         * 设置访问的模式名称
         * 注意: 适用于存在模式的方言, 例：Oracle, PostgreSQL, SQLServer
         * @param names 模式名称
         * @return
         */
        public Builder schema(String names){
            if(dialect==SchemaDialect.Oracle || dialect==SchemaDialect.PostgreSQL || dialect==SchemaDialect.SQLServer) {
                this.schema = names;
            }
            return this;
        }

        /**
         * 设置Informix的服务器名称
         * @param serverName 服务器名称
         * @return
         */
        public Builder server(String serverName){
            if(dialect == SchemaDialect.Informix){
                this.schema = serverName;
            }
            return this;
        }
        /**
         * 连接使用的帐号
         * @param username 用户名
         * @param password 密码
         * @return
         */
        public ResultSupplier account(String username, String password){
            return new ResultSupplier(this, username, password);
        }

        /**
         * 无需帐号结果集供应器, 例: SQLite
         * @return
         */
        public ResultSupplier anonymous(){
            return new ResultSupplier(this, null, null);
        }
    }
}
